package com.partsoft.dits.utils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;

import org.springframework.ldap.control.PagedResultsDirContextProcessor;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.AttributesMapperCallbackHandler;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.NameClassPairCallbackHandler;
import org.springframework.ldap.core.support.CountNameClassPairCallbackHandler;
import org.springframework.util.Assert;
import com.partsoft.dits.utils.DbUtils.DataActionAppendResultFilter;

import com.partsoft.dits.message.DataAction;
import com.partsoft.dits.message.DataProperty;

public class LdapUtils {

	public static final int MAX_PAGESIZE = 10000;

	public static List<Map<DataProperty, String>> getEntities(ContextSource ctx, String parentDN, String entityFilter,
			int from, int length, List<DataProperty> propertys, DataAction action) {
		return getEntities(ctx, parentDN, entityFilter, from, length, propertys, new DataActionAppendResultFilter(action));
	}

	
	public static List<Map<DataProperty, String>> getEntities(ContextSource ctx, String parentDN, String entityFilter,
			int from, int length, List<DataProperty> propertys) {
		return getEntities(ctx, parentDN, entityFilter, from, length, propertys, MessageDataItemResultFilter.NOP);
	}
	
	public static List<Map<DataProperty, String>> getEntities(ContextSource ctx, String parentDN, String entityFilter,
			int from, int length, List<DataProperty> propertys, MessageDataItemResultFilter filter) {
		Assert.notNull(ctx);
		Assert.hasText(parentDN);
		Assert.notEmpty(propertys);
		return getEntities(ctx, parentDN, entityFilter, from, length, propertys.toArray(new DataProperty[0]), filter);
	}

	protected static SearchControls buildSearchControls(DataProperty[] propertys) {
		return buildSearchControls(propertys, false);
	}

	protected static SearchControls buildSearchControls(DataProperty[] propertys, boolean resultUnLimited) {
		SearchControls cons = new SearchControls();
		cons.setSearchScope(SearchControls.SUBTREE_SCOPE);
		if (resultUnLimited) {
			cons.setCountLimit(0);
		}
		if (propertys != null && propertys.length > 0) {
			List<String> atts = new ArrayList<String>(propertys.length);
			for (DataProperty py : propertys) {
				atts.add(py.getPropertyName());
			}
			cons.setReturningAttributes(atts.toArray(new String[0]));
		} else {
			cons.setReturningAttributes(null);
		}
		return cons;
	}

	public static int countEntities(ContextSource ctx, String parentDN, String entityFilter) {
		int result = 0;
		SearchControls cons = buildSearchControls(null, true);
		CountNameClassPairCallbackHandler tempHandler = new CountNameClassPairCallbackHandler();
		LdapTemplate ldaphelper = new LdapTemplate(ctx);
		try {
			ldaphelper.afterPropertiesSet();
		} catch (Throwable e) {
			throw new IllegalArgumentException(e.getMessage(), e);
		}

		ldaphelper.search(parentDN, entityFilter, cons, tempHandler);
		result += tempHandler.getNoOfRows();
		return result;
	}
	
	public static List<Map<DataProperty, String>> getEntities(ContextSource ctx, String parentDN, String entityFilter,
			int pageIndex, int pageSize, DataProperty[] propertys, DataAction action) {
		return getEntities(ctx, parentDN, entityFilter, pageIndex, pageSize, propertys, new DataActionAppendResultFilter(action));
	}
	
	public static List<Map<DataProperty, String>> getEntities(ContextSource ctx, String parentDN, String entityFilter,
			int pageIndex, int pageSize, DataProperty[] propertys) {
		return getEntities(ctx, parentDN, entityFilter, pageIndex, pageSize, propertys, MessageDataItemResultFilter.NOP);
	}

	@SuppressWarnings("unchecked")
	public static List<Map<DataProperty, String>> getEntities(ContextSource ctx, String parentDN, String entityFilter,
			int pageIndex, int pageSize, DataProperty[] propertys, MessageDataItemResultFilter filter) {
		Assert.notNull(ctx);
		Assert.hasText(parentDN);
		Assert.notEmpty(propertys);
		Assert.isTrue(pageIndex >= 0);
		Assert.isTrue(pageSize > 0);

		SearchControls cons = buildSearchControls(propertys, true);
		EntityAttributesMapper attributesMapper = new EntityAttributesMapper(propertys, filter);
		AttributesMapperCallbackHandler tempHandler = new AttributesMapperCallbackHandler(attributesMapper);
		PagedResultsDirContextProcessor paged = new PagedResultsDirContextProcessor(pageSize);

		LdapTemplate ldaphelper = new LdapTemplate(ctx);
		try {
			ldaphelper.afterPropertiesSet();
		} catch (Throwable e) {
			throw new IllegalArgumentException(e.getMessage(), e);
		}
		ldaphelper.search(parentDN, entityFilter, cons, tempHandler, paged);
		if (pageIndex > 0) {
			for (int i = 0; i < pageIndex; i++) {
				Assert.notNull(paged.getCookie());
				paged = new PagedResultsDirContextProcessor(pageSize, paged.getCookie());
				if (i < pageIndex - 1) {
					ldaphelper.search(parentDN, entityFilter, cons, VoidNameClassPairCallbackHandler.INSTANCE, paged);
				} else {
					tempHandler = new AttributesMapperCallbackHandler(attributesMapper);
					ldaphelper.search(parentDN, entityFilter, cons, tempHandler, paged);
				}
			}
		}
		return tempHandler.getList();
	}
	
	public static List<Map<DataProperty, String>> getEntities(ContextSource ctx, String parentDN, String entityFilter,
			List<DataProperty> propertys) {
		return getEntities(ctx, parentDN, entityFilter, propertys, MessageDataItemResultFilter.NOP);
	}
	
	public static List<Map<DataProperty, String>> getEntities(ContextSource ctx, String parentDN, String entityFilter,
			List<DataProperty> propertys, DataAction action) {
		return getEntities(ctx, parentDN, entityFilter, propertys, new DataActionAppendResultFilter(action));
	}

	public static List<Map<DataProperty, String>> getEntities(ContextSource ctx, String parentDN, String entityFilter,
			List<DataProperty> propertys, MessageDataItemResultFilter filter) {
		Assert.notNull(ctx);
		Assert.hasText(parentDN);
		Assert.notEmpty(propertys);
		return getEntities(ctx, parentDN, entityFilter, propertys.toArray(new DataProperty[0]), filter);
	}
	
	public static List<Map<DataProperty, String>> getEntities(ContextSource ctx, String parentDN, String entityFilter,
			DataProperty[] propertys, DataAction action) {
		Assert.notNull(action);
		return getEntities(ctx, parentDN, entityFilter, propertys, new DataActionAppendResultFilter(action));
	}
	
	public static List<Map<DataProperty, String>> getEntities(ContextSource ctx, String parentDN, String entityFilter,
			DataProperty[] propertys) {
		return getEntities(ctx, parentDN, entityFilter, propertys, MessageDataItemResultFilter.NOP);
	}

	@SuppressWarnings("unchecked")
	public static List<Map<DataProperty, String>> getEntities(ContextSource ctx, String parentDN, String entityFilter,
			DataProperty[] propertys, MessageDataItemResultFilter filter) {
		Assert.notNull(ctx);
		Assert.hasText(parentDN);
		Assert.notEmpty(propertys);

		SearchControls cons = buildSearchControls(propertys, true);
		EntityAttributesMapper attributesMapper = new EntityAttributesMapper(propertys, filter);
		AttributesMapperCallbackHandler tempHandler = new AttributesMapperCallbackHandler(attributesMapper);

		LdapTemplate ldaphelper = new LdapTemplate(ctx);
		try {
			ldaphelper.afterPropertiesSet();
		} catch (Throwable e) {
			throw new IllegalArgumentException(e.getMessage(), e);
		}
		ldaphelper.search(parentDN, entityFilter, cons, tempHandler);
		return tempHandler.getList();
	}

	protected static class EntityAttributesMapper implements AttributesMapper {

		private final DataProperty properties[];
		
		private final MessageDataItemResultFilter dataItemResultFilter;
		
		public EntityAttributesMapper(DataProperty properties[], MessageDataItemResultFilter dataItemResultFilter) {
			this.properties = properties;
			this.dataItemResultFilter = dataItemResultFilter;
		}

		@SuppressWarnings("unchecked")
		public Object mapFromAttributes(Attributes attributes) throws NamingException {
			Map<DataProperty, String> result = null;
			if (properties != null && properties.length > 0) {
				result = new HashMap<DataProperty, String>(properties.length);
				for (DataProperty prop : properties) {
					Attribute att = attributes.get(prop.getPropertyName());
					if (att != null) {
						boolean appended = false;
						StringBuilder sbf = new StringBuilder();
						NamingEnumeration<?> objEnum = att.getAll();
						while(objEnum.hasMore()) {
							if (appended) sbf.append(";");
							appended = true;
							sbf.append(objEnum.next().toString());
						}
						result.put(prop, sbf.toString());
					}
				}
			} else {
				result = Collections.EMPTY_MAP;
			}
			if (dataItemResultFilter != null)
				dataItemResultFilter.filter(result);
			return result;
		}
	}

	protected static class VoidNameClassPairCallbackHandler implements NameClassPairCallbackHandler {

		public static VoidNameClassPairCallbackHandler INSTANCE = new VoidNameClassPairCallbackHandler();

		public void handleNameClassPair(NameClassPair nameclasspair) {
			
		}
	}

}
